package weeny.pre.display;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.AbstractButton;
import javax.swing.Box;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JRadioButton;
import javax.swing.border.TitledBorder;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.DefaultFontMapper;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.text.pdf.DefaultFontMapper.BaseFontParameters;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.render.LabelRenderer;
import prefuse.visual.VisualItem;
import sun.font.FontManager;
import weeny.pre.util.Geom;
import weeny.util.io.FileUtil;
import weeny.util.io.IOUtil;
import weeny.util.logs.Debug;

class PdfDisplay extends Display{
	//making the constructors protected, 
	//so please do not directly use this class, but to inherit this.
	protected PdfDisplay() {}
	protected PdfDisplay(Visualization vis){
		super(vis);
	}
	private String type(String file){
		return file.substring(file.length()-3); 
	}
	public void saveAll(String file){
		try {
			saveAll(new FileOutputStream(file), type(file));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	public void saveAll(OutputStream os, String format){
		Rectangle2D rect = m_vis.getBounds(Visualization.ALL_ITEMS);
		saveBox(os, rect, format);
	}
	public void saveVisual(String file){
		try {
			saveVisual(new FileOutputStream(file), type(file));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	public void saveVisual(OutputStream os, String format){
		Rectangle2D rect = this.getItemBounds();
		saveBox(os, rect, format);
	}
	public void saveView(String file){
		try {
			saveView(new FileOutputStream(file), type(file));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	public void saveView(OutputStream os, String format){
		Rectangle2D rect = viewRect();
		saveBox(os, rect, format);
	}

	public void saveBox(String file, Rectangle2D rect){
		try {
			saveBox(new FileOutputStream(file), rect, type(file));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	@Override
	protected void clearRegion(Graphics2D g, Rectangle2D r) {
		if (savingPDF) {
			super.firePrePaint(g);
		}
		else{
			super.clearRegion(g, r);
		}
	}
	
	private DefaultFontMapper dfm = new DefaultFontMapper();

	/**
	 * Show all the fonts you can choose
	 */
	public void showAllAvailableFonts(){
		GraphicsEnvironment ge = 
			GraphicsEnvironment.getLocalGraphicsEnvironment();
		String[] fontNames = ge.getAvailableFontFamilyNames();
		for(String name : fontNames)
			System.out.println(name);
	}

	/**
	 * Convenience version of {@link #putPdfFontFile(String, String, String)} 
	 * by using encoding {@link BaseFont#CP1252}.
	 */
	public void putPdfFontFile(String name, String path){
		putPdfFontFile(name, path, BaseFont.CP1252);
	}
	/**
	 * Specify the complete path and encoding for a specific fontName used in
	 * this specific program. 
	 * @param name the value returned by {@link Font#getFontName()}
	 * @param path the file path for the spacific font name, such as C:/Windows/
	 * Fonts/fontName.ttf. The file name should end with ttf, ttc, or otf.
	 * @param encoding the encoding for the font file, it could be either
	 * {@link BaseFont#CP1252} for English or {@link BaseFont#IDENTITY_H} for
	 * Chinese.
	 */
	public void putPdfFontFile(String name, String path, String encoding){
		if (path.endsWith(".ttc")) {
			path += ",0";
		}
		BaseFontParameters bfp = new BaseFontParameters(path);
		if (!encoding.equals(BaseFont.CP1252) && 
				!encoding.equals(BaseFont.IDENTITY_H)){
			throw new IllegalArgumentException("Wrong encoding");
		}
		bfp.encoding = encoding;
		dfm.putName(name, bfp);
	}

	/**
	 * Specify an alias name for the specific font name. So the alias will be
	 * treated as the font name.
	 */
	public void putPdfFontAlias(String alias, String name){
		dfm.putAlias(alias, name);
	}
	
	private void checkPdfFonts(){
		Iterator<VisualItem> it = getVisualization().items();
		if (IOUtil.isMac()) {
			while (it.hasNext()){
				VisualItem vi = it.next();
				if (vi.getRenderer() instanceof LabelRenderer){
					Font font = vi.getFont();
					if (dfm.getBaseFontParameters(font.getFontName()) != null) {
						continue;
					}
					Debug.info("Search for font name: "+font.getFontName());
					String name = font.getName();
					File fontFolder = new File("/Library/Fonts");
					List<String> fileNames = FileUtil.recFiles(fontFolder);
					int index = 0;
					for (; index < fileNames.size(); index++) {
						String string = fileNames.get(index);
						if (string.toLowerCase().contains(name.toLowerCase())) {
							putPdfFontFile(font.getFontName(), string);
							Debug.info(1, "Load font file: "+string);
							break;
						}
					}
					if (index == fileNames.size()) {
						Debug.prob(1, "Cannot find font file ");
					}
				}
			}
			return;
		}
		while (it.hasNext()){
			VisualItem vi = it.next();
			if (vi.getRenderer() instanceof LabelRenderer){
				Font font = vi.getFont();
				String name = font.getFontName();
				if (dfm.getBaseFontParameters(name) != null){
					continue;
				}
				String path = FontManager.getFontPath(true);
				String fileName = FontManager.getFileNameForFontName(name);
				if (path == null || fileName == null) {
					Debug.prob("Cannot find font file for "+name);
					continue;
				}
				path = path + IOUtil.SLASH+ fileName;
				Debug.info("Loading font file: "+path);
				putPdfFontFile(font.getFontName(), path);
			}
		}
	}
	
	private boolean savingPDF = false;
	public void saveBox(OutputStream os, Rectangle2D rect, String format){
		Point2D abs = new Point2D.Double();
		getAbsoluteCoordinate(new Point2D.Double(0,0), abs);
		Rectangle2D view = viewRect();
		double heightScale = view.getHeight()/rect.getHeight();
		double widthScale = view.getWidth()/rect.getWidth();
		double scale = Math.min(heightScale, widthScale);
		super.panAbs(view.getX()-rect.getX(), view.getY()-rect.getY());
		super.zoom(new Point2D.Double(0,0), scale);
		boolean q = isHighQuality();
		setHighQuality(true);
		
		float heightOverWidth = (float) (rect.getHeight()/rect.getWidth());
		Rectangle size = new Rectangle(getWidth(), heightOverWidth*getWidth());
		if (heightScale<widthScale) {
			size = new Rectangle(getHeight()/heightOverWidth, getHeight());
		}
		try {
			Graphics2D g2 = null;
            // get an image to draw into
            Dimension d = new Dimension(getWidth(),getHeight());
			if (format.toLowerCase().equals("pdf")) {
				//prepare for the pdf fonts
				checkPdfFonts();
				Document doc = new Document(size);
				PdfWriter writer = PdfWriter.getInstance(doc, os);
				doc.open();
				PdfContentByte cb = writer.getDirectContent();				
				g2 = cb.createGraphics(size.getWidth(), size.getHeight(), dfm);
				//setting the flag to true, so clearRegin function can skip
				//the filling background step, so the pdf file has a
				//transparent background
				savingPDF = true;
				g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
						RenderingHints.VALUE_ANTIALIAS_ON);
				g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
						RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
            	paintDisplay(g2, d);
    			g2.dispose();
    			doc.close();
    			//setting the flag back to false
    			savingPDF = false;
			}
            else {
            	BufferedImage image = new BufferedImage((int)size.getWidth(), 
						(int) size.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
				g2 = image.createGraphics();
				paintDisplay(g2, d);
	            try {
					ImageIO.write(image, format, os);
				} catch (IOException e) {
					e.printStackTrace();
				}
            }
		} catch (DocumentException e1) {
			e1.printStackTrace();
		}
        setHighQuality(q);
        super.zoom(new Point2D.Double(0,0), 1/scale);
        super.panAbs(rect.getX()-view.getX(), rect.getY()-view.getY());
	
	}
	private Rectangle2D viewRect(){
		Point2D abs = new Point2D.Double();
		getAbsoluteCoordinate(Geom.PNT_ZERO, abs);
		Rectangle2D rect = new Rectangle2D.Double(abs.getX(), abs.getY(),0,0);
		Dimension dim = getSize();
		Point2D lowRight = new Point2D.Double(dim.getWidth(), dim.getHeight());
		getAbsoluteCoordinate(lowRight, abs);
		rect.add(abs);
		return rect;
	}
	
	public Box createSaveImageBox(){
		return createSaveImageBox(null);
	}
	
	/**
	 * create a save image control of the view. There are three choice to save.
	 * 1) "view" will save everything in the current display window and cut out
	 *    everything else.
	 * 2) "all items" will save everything in the backend data, including the
	 *    invisible ones.
	 * 3) "visual items" will save everything visible in the backend data.
	 * 
	 * On the other hand, you can also choose "save as pdf" or "save as png".
	 * 
	 * NOTE: the default destination file is decided by IOUtil, see this class
	 *       for more details. Basically, hold "alt", the file will be save to
	 *       the C:/javaDebug/(current time)/ folder (For Mac system, it is the 
	 *       the desktop folder), and the file name is decided by the time you
	 *       click the button. If "alt" is not hold, a file chooser will show
	 *       up to ask for a file name.
	 */
	public Box createSaveImageBox(String title){
		Box saveBox = Box.createVerticalBox();
		String infoMsg = title == null ? "":"("+title+")";
		saveBox.setBorder(new TitledBorder("Save Image "+ infoMsg));
		final ButtonGroup bg = new ButtonGroup();
		final JRadioButton viewRadio = new JRadioButton("view", true);
		final JRadioButton allRadio = new JRadioButton("all items");
		final JRadioButton visualRadio = new JRadioButton("visible items");
		bg.add(viewRadio);
		bg.add(allRadio);
		bg.add(visualRadio);
		final JButton savePdf = new JButton("Save as PDF");
//		savePdf.setToolTipText("save to C:/javaDebug/");
		final JButton savePng = new JButton("Save as PNG");
//		savePng.setToolTipText("save to C:/javaDebug/");
		ActionListener saveActionListener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String dft = IOUtil.outputFolder()+IOUtil.timeString();
				String fileName = IOUtil.targetFile(e, dft, false);
				if (fileName == null) {
					return;
				}
				if (e.getSource() == savePdf && !fileName.endsWith("pdf")) {
					fileName += ".pdf";
				}
				if (e.getSource() == savePng && !fileName.endsWith("png")) {
					fileName += ".png";
				}
				if (viewRadio.isSelected()) {
					saveView(fileName);
				}
				else if (allRadio.isSelected()) {
					saveAll(fileName);
				}
				else if (visualRadio.isSelected()) {
					saveView(fileName);
				}
				Debug.info("image is save to "+fileName);
			}
		};
		savePdf.addActionListener(saveActionListener);
		savePng.addActionListener(saveActionListener);
		Box optionBox = Box.createHorizontalBox();
		for (Enumeration e = bg.getElements(); e.hasMoreElements();){
			optionBox.add((AbstractButton)e.nextElement());
		}
		Box buttonBox = Box.createHorizontalBox();
		buttonBox.add(savePdf);
		buttonBox.add(savePng);
		saveBox.add(optionBox);
		saveBox.add(buttonBox);
		saveBox.add(Box.createVerticalStrut(1));
		return saveBox;
	}
}
